package com.gin.kotlin.no_gay_alliance.provider.ngaphp

import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.type.TypeFactory
import com.gin.kotlin.no_gay_alliance.app.App
import com.gin.kotlin.no_gay_alliance.callback.RCallback
import com.gin.kotlin.no_gay_alliance.utils.FileCacheUtils
import com.gin.nga.callback.NgaCallback
import com.gin.nga.main.NgaApi
import com.gin.nga.response.base.NgaResponse
import com.gin.retrofit.utils.JacksonUtils
import retrofit2.Call
import retrofit2.Response
import java.io.File
import java.util.concurrent.TimeUnit

/**
 * 带缓存的数据提供者
 * @param P 参数类型
 * @param R 源响应类型
 * @param D 缓存数据类型
 * @property expires 过期时长，默认6小时
 * @property TAG 日志TAG
 * @constructor
 */
abstract class AbstractCachedDataProvider<P, R, D : Any>(
    private val javaType: JavaType,
    private val expires: Long = TimeUnit.HOURS.toSeconds(6)
) {
    @Suppress("PropertyName")
    protected val TAG: String = this::class.java.simpleName

    /**
     * 本提供者的缓存目录相对路径
     */
    abstract val dir: String

    /**
     * 异步请求获取数据
     * @param params 请求参数
     * @param userId 用户ID
     * @param api NgaApi
     * @param ignoreCache 是否忽略缓存
     * @param callback 回调
     */
    fun enqueue(
        params: P? = null,
        userId: Long = App.INSTANCE.account.userId,
        api: NgaApi = App.INSTANCE.ngaApi,
        ignoreCache: Boolean = false,
        callback: RCallback<D, P>? = null,
    ) {
        val relativePath = "$dir/${obtainRelativePath(userId, params)}"
        if (!ignoreCache) {
            val data: D? = FileCacheUtils.readCache(relativePath, expires, javaType)
            if (data != null) {
                callback?.onSuccess(data, ignoreCache)
                return
            }
        }
        // 如果忽略缓存，或缓存未命中，执行请求
        requestMethod(api, params).enqueue(object : NgaCallback<R> {
            override fun onSuccess(call: Call<NgaResponse<R>>, response: Response<NgaResponse<R>>, body: NgaResponse<R>?, data: R?) {
                val res = transform(data!!)
                FileCacheUtils.writeCache(relativePath, res)
                onSuccess(body)
                callback?.onSuccess(res, ignoreCache)
            }

            override fun onError(call: Call<NgaResponse<R>>, response: Response<NgaResponse<R>>, body: NgaResponse<R>?, errorMessage: String) {
                callback?.onError(errorMessage)
            }

            override fun onJsonParseException(call: Call<NgaResponse<R>>, e: JsonParseException) {
                callback?.onFailed(ignoreCache, params)
            }
        })
    }

    /**
     * 同步请求获取数据
     * @param params 请求参数
     * @param userId 用户ID
     * @param api NgaApi
     * @param ignoreCache 是否忽略缓存
     */
    fun execute(
        params: P? = null,
        userId: Long = App.INSTANCE.account.userId,
        api: NgaApi = App.INSTANCE.ngaApi,
        ignoreCache: Boolean = false,
    ): D {
        val relativePath = "$dir/${obtainRelativePath(userId, params)}"
        if (!ignoreCache) {
            val data: D? = FileCacheUtils.readCache(relativePath, expires, javaType)
            if (data != null) {
                return data
            }
        }
        // 如果忽略缓存，或缓存未命中，执行请求
        val response = requestMethod(api, params).execute()
        val body = response.body()
        val res = transform(body?.data!!)
        FileCacheUtils.writeCache(relativePath, res)
        onSuccess(body)
        return res
    }

    /**
     * 请求成功的回调
     * @param data R
     */
    open fun onSuccess(data: NgaResponse<R>?) {

    }

    /**
     * 将源生响应转换为有效数据的方法
     * @param rawData R
     * @return D
     */
    abstract fun transform(rawData: R): D

    /**
     * 请求方法
     * @param ngaApi NgaApi
     * @param params 参数对象
     * @return Call<NgaResponse<R>>
     */
    abstract fun requestMethod(ngaApi: NgaApi, params: P?): Call<NgaResponse<R>>

    /**
     * 保存的文件相对路径
     * @param userId 当前用户ID
     * @param params 请求参数
     * @return String
     */
    abstract fun obtainRelativePath(userId: Long, params: P?): String

    /**
     * 清空缓存
     */
    fun clear() {
        File(App.INSTANCE.cacheDir, dir).deleteRecursively()
    }

    companion object {
        val TYPE_FACTORY: TypeFactory = JacksonUtils.MAPPER.typeFactory
    }
}